/**
 * Copyright © 2012, Grid Protection Alliance.  All Rights Reserved.
 *
 * Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
 * the NOTICE file distributed with this work for additional information regarding copyright ownership.
 * The GPA licenses this file to you under the Eclipse Public License -v 1.0 (the "License"); you may
 * not use this file except in compliance with the License. You may obtain a copy of the License at:
 *
 * http://www.opensource.org/licenses/eclipse-1.0.php
 *
 * Unless agreed to in writing, the subject software distributed under the License is distributed on an
 * "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
 * License for the specific language governing permissions and limitations.
 */
package org.gridprotectionalliance.oam.proxy.impl

import org.gridprotectionalliance.oam.proxy._
import java.util.concurrent.ExecutorService
import com.automatak.archivist.api.historian._
import com.automatak.archivist.protocol.types.Sample
import com.automatak.executor4s._
import com.automatak.archivist.api.historian.ReadQuery
import com.automatak.archivist.protocol.types.SampleWithId
import com.automatak.archivist.api.historian.MetaData
import com.automatak.log4s.Logging
import com.automatak.archivist.protocol.proto.Requests.{ SortOrder, ListDirection }

case class ProxyHistorianConfig(metadataReadSize: Int)

class ProxyHistorian(exe: ExecutorService, store: ConfigStore, cfg: ProxyHistorianConfig) extends HistorianConfigService with Historian with Logging {

  private val state = new ProxyState

  /* Methods for adding/remove HistorianFactory's */

  def addFactory(factory: HistorianClientFactory): Unit = {
    state.addFactory(factory)
    val configs = store.getConfigForType(factory.typ)
    configs.foreach { cfg => instantiate(factory, cfg) }
  }

  def removeFactory(factory: HistorianClientFactory): Unit = {
    state.removeFactory(factory.typ).foreach { case (n, rec) => rec.client.shutdown() }
  }

  /* --- Implement HistorianConfigService --- */

  def add(cfg: HistorianConfig): Unit = {
    val factory = state.addHistorianConfig(cfg)
    store.insert(cfg)
    factory.foreach(fr => instantiate(fr.factory, cfg))
  }

  def remove(name: String): Unit = {
    val record = state.removeHistorian(name)
    store.removeByName(name)
    record.client.shutdown()
  }

  def list: List[HistorianConfig] = {
    store.list()
  }

  /* --- Implement Historian --- */

  def insert(ms: Traversable[SampleWithId]): Future[Option[Exception]] = {
    try {
      val futures = state.partitionOperation(ms)(s => s.id).map(io => io.hist.insert(io.values))
      Futures.gather(exe, futures).map { x =>
        val exceptions = x.flatten
        if (exceptions.isEmpty) None
        else Some(new AggregateException("Multiple exceptions occured during insert", exceptions))
      }
    } catch {
      case ex: Exception =>
        val f = exe.future[Option[Exception]]
        f.set(Some(ex))
        f
    }
  }

  def getMetaData(from: Option[String], until: Option[String], limit: Int, dir: ListDirection, sort: SortOrder): Future[Result[List[MetaData]]] = {
    val f = exe.future[Result[List[MetaData]]]
    val records = state.getMetaData(from, until, limit, dir, sort)
    f.set(Success(records.map(mdr => mdr.md)))
    f
  }

  def getHistory(id: String, query: ReadQuery): Future[Result[Traversable[Sample]]] = {
    state.getHistorianForId(id) match {
      case Some(hist) =>
        hist.client.getHistory(id, query)
      case None =>
        val f = exe.future[Result[Traversable[Sample]]]
        f.set(Failure("No active historian found for id: " + id))
        f
    }
  }

  def getCurrentState(ids: List[String]): Future[Result[List[SampleWithId]]] = {
    try {
      val futures = state.partitionOperation(ids)(s => s).map(io => io.hist.getCurrentState(io.values))
      Futures.gather(exe, futures).map { x =>
        val exs = for { Failure(ex) <- x } yield ex
        if (exs.nonEmpty) Failure(new AggregateException("Multiple failures occured getting current state", exs))
        else {
          val successes = for { Success(values) <- x.toList } yield values
          Success(successes.flatten)
        }
      }
    } catch {
      case ex: Exception =>
        val f = exe.future[Result[List[SampleWithId]]]
        f.set(Failure(ex))
        f
    }
  }

  /* -- Helper methods -- */

  private def instantiate(factory: HistorianClientFactory, config: HistorianConfig): Unit = {
    val record = HistorianRecord(config, factory.create(config.connString))
    state.addActiveHistorian(record)
    loadMetaData(record.cfg.name, record.client)
  }

  private def loadMetaData(name: String, client: HistorianClient): Unit = {

    def recurse(from: Option[String]): Unit = {
      def onResult(res: Result[List[MetaData]]): Unit = res match {
        case Success(md) =>
          state.addMetadata(md, name)
          if (md.size == cfg.metadataReadSize) recurse(Some(md.last.id))
        case Failure(ex) =>
          logger.error("Error retrieving metadata", ex)
      }
      client.getMetaData(from, None, cfg.metadataReadSize, ListDirection.LEFT, SortOrder.NORMAL).listen(onResult)
    }

    recurse(None)
  }
}
